#include "DirectX11.h"
#include "Renderable.h"
#include "View.h"
#include "DDSTextureLoader.h"

#define SAFE_RELEASE(ptr) if(ptr){ ptr->Release(); ptr = nullptr;}

#define TESTACTIVE 0


//Move to Post Process
#include "PostProcessVS.csh"
#include "PostProcessGS.csh"
#include "PostProcessPS.csh"
#include "LightRepresentationVS.csh"
#include "LightRepresentationGS.csh"
#include "LightRepresentationPS.csh"
#include "HelperD3D.h"


DirectX11* DirectX11::instance = nullptr;

DirectX11* DirectX11::GetInstance(){
	if(instance == nullptr)
		instance = new DirectX11();

	return instance;
}

void DirectX11::DeleteInstance(){

	std::map<std::string,ID3D11ShaderResourceView*>::iterator iter = textures.begin();

	while(textures.size() > 0){
		textures.begin()->second->Release();
		textures.erase(textures.begin());
	}
		
	SAFE_RELEASE(device);
	SAFE_RELEASE(swapChain);
	SAFE_RELEASE(deviceContext);
	SAFE_RELEASE(rtv);
	SAFE_RELEASE(dsv);
	SAFE_RELEASE(deferredContext);

	SAFE_RELEASE(postProcessRTV);
	SAFE_RELEASE(postProcessDSV);
	SAFE_RELEASE(postProcessVS);
	SAFE_RELEASE(postProcessGS);
	SAFE_RELEASE(postProcessPS);
	SAFE_RELEASE(lightRepresentationVS);
	SAFE_RELEASE(lightRepresentationGS);
	SAFE_RELEASE(lightRepresentationPS);
	SAFE_RELEASE(postProcessTexture);
	SAFE_RELEASE(samplerState);

	delete instance;
	instance = nullptr;
}



DirectX11::DirectX11(void){}
DirectX11::~DirectX11(void){}

bool DirectX11::Initialize(HWND hWnd){

	//Create Device And Swap chain
	DXGI_SWAP_CHAIN_DESC scDesc;
	ZeroMemory(&scDesc, sizeof(DXGI_SWAP_CHAIN_DESC));		//Clear memory to 0
	scDesc.BufferCount = 1;
	scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	scDesc.BufferDesc.Width = WINDOW_WIDTH;
	scDesc.BufferDesc.Height = WINDOW_HEIGHT;
	scDesc.BufferDesc.RefreshRate.Numerator = 60;
	scDesc.BufferDesc.RefreshRate.Denominator = 1;
	scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scDesc.OutputWindow = hWnd;
	scDesc.SampleDesc.Count = 1;
	scDesc.SampleDesc.Quality = 0;
	scDesc.Windowed = TRUE;

	//Set D3D Feature Level
	D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
	D3D_FEATURE_LEVEL featureLevelsSupported;
	UINT numFeatureLevels = 1;

	HRESULT hr;

#if _DEBUG
	hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE,NULL,
		D3D11_CREATE_DEVICE_DEBUG,&featureLevel,numFeatureLevels,D3D11_SDK_VERSION,
		&scDesc,&swapChain,&device,&featureLevelsSupported,&deviceContext);
#else
	hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE,NULL,
		NULL,&featureLevel,numFeatureLevels,D3D11_SDK_VERSION,
		&scDesc,&swapChain,&device,&featureLevelsSupported,&deviceContext);
#endif

	if(FAILED(hr)){
		ERRORMESSAGE(L"ERROR: Failed to create the Device and Swap Chain");
		//return false;
	}

	hr = device->CreateDeferredContext(0, &deferredContext);
	ERRORCHECK(hr, L"ERROR: Failed to create the deferred context");

	//Create Depth Stencil View
	ID3D11Texture2D* zBuffer;

	D3D11_TEXTURE2D_DESC dDesc;
	ZeroMemory(&dDesc,sizeof(D3D11_TEXTURE2D_DESC));
	dDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dDesc.Format = DXGI_FORMAT_D32_FLOAT;
	dDesc.Width = WINDOW_WIDTH;
	dDesc.Height = WINDOW_HEIGHT;
	dDesc.MipLevels = 1;
	dDesc.ArraySize = 1;
	dDesc.SampleDesc.Count = 1;
	dDesc.SampleDesc.Quality = 0;
	dDesc.Usage = D3D11_USAGE_DEFAULT;
	dDesc.CPUAccessFlags = NULL;
	dDesc.MiscFlags = 0;

	hr = device->CreateTexture2D(&dDesc,NULL,&zBuffer);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to create the zBuffer");

	//return false;

	D3D11_TEX2D_DSV texdsv;
	texdsv.MipSlice = 0;

	D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
	ZeroMemory(&dsvDesc,sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
	dsvDesc.Flags = 0;
	dsvDesc.Format = dDesc.Format;
	dsvDesc.Texture2D = texdsv;
	dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;

	hr = device->CreateDepthStencilView(zBuffer,&dsvDesc,&dsv);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to create the DSV.");
	//return false;

	ID3D11Texture2D* backbuffer;
	hr = swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),(LPVOID*) &backbuffer);

	if(FAILED(hr))
		ERRORMESSAGE(L"ERROR: Failed to get the backbuffer.");


	hr = device->CreateRenderTargetView(backbuffer,NULL,&rtv);
	ERRORCHECK(hr,L"ERROR: Failed to create RTV.");

	D3D11_TEXTURE2D_DESC bbDesc;
	backbuffer->GetDesc(&bbDesc);
	viewport.Height = (FLOAT)bbDesc.Height;
	viewport.Width = (FLOAT)bbDesc.Width;
	viewport.MinDepth = 0;
	viewport.MaxDepth = 1;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;

	for(int i = 0; i < NUMCONSTBUFFS; i++)
		constantBuffers[i] = nullptr;

	LoadTexture(L"182.dds","base");
	LoadTexture(L"182_norm.dds","normal");
	
	InitializePostProcessStuff();

	hr = device->CreateVertexShader(PostProcessVS,sizeof(PostProcessVS),NULL,&postProcessVS);
	ERRORCHECK(hr,L"ERROR: Failed to create post process VS");
	hr = device->CreateGeometryShader(PostProcessGS,sizeof(PostProcessGS),NULL,&postProcessGS);
	ERRORCHECK(hr,L"ERROR: Failed to create post process GS");
	hr = device->CreatePixelShader(PostProcessPS,sizeof(PostProcessPS),NULL,&postProcessPS);
	ERRORCHECK(hr,L"ERROR: Failed to create post process PS");

	hr = device->CreateVertexShader(LightRepresentationVS,sizeof(LightRepresentationVS),NULL,&lightRepresentationVS);
	ERRORCHECK(hr,L"ERROR: Failed to create post LR VS");
	hr = device->CreateGeometryShader(LightRepresentationGS,sizeof(LightRepresentationGS),NULL,&lightRepresentationGS);
	ERRORCHECK(hr,L"ERROR: Failed to create post LR GS");
	hr = device->CreatePixelShader(LightRepresentationPS,sizeof(LightRepresentationPS),NULL,&lightRepresentationPS);
	ERRORCHECK(hr,L"ERROR: Failed to create post LR PS");

	D3D11_SAMPLER_DESC samplerDesc;
	ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC));
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.MinLOD = -FLT_MAX;
	samplerDesc.MaxLOD = FLT_MAX;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;

	hr = device->CreateSamplerState(&samplerDesc,&samplerState);
	ERRORCHECK(hr,L"ERROR: Failed to create the Sampler State");

	SAFE_RELEASE(zBuffer);
	SAFE_RELEASE(backbuffer);
	return true;
}

bool DirectX11::Shutdown(){
	DeleteInstance();		
	return true;
}

void DirectX11::Draw(Renderable* object,View* camera){

	RenderInfo info = object->GetRenderInformation();
	D3D11_VIEWPORT vp = camera->GetViewPort();
	ID3D11Buffer* sceneBuffer = camera->GetBuffer();
	SCENE sScene = *camera->GetStruct();

#if 0
	deferredContext->OMSetRenderTargets(1, &rtv, dsv);
	deferredContext->RSSetViewports(1, &viewport);
#else
	deferredContext->OMSetRenderTargets(1, &postProcessRTV, postProcessDSV);
	deferredContext->RSSetViewports(1, &postProcessViewport);
#endif

	deferredContext->IASetVertexBuffers(0, info.numBuffers, info.buffers, info.strides, info.offsets);
	deferredContext->IASetIndexBuffer(info.indexBuffer, DXGI_FORMAT_R32_UINT, 0);
	deferredContext->IASetInputLayout(info.inputLayout);
	deferredContext->IASetPrimitiveTopology(info.topology);
	deferredContext->VSSetShader(info.vs, NULL, NULL);
	deferredContext->PSSetShader(info.ps, NULL, NULL);

	if(info.srv)
		deferredContext->PSSetShaderResources(0,1,&info.srv);
	if(info.normalMapSRV)
		deferredContext->PSSetShaderResources(1,1,&info.normalMapSRV);
	if(info.samplerState)
		deferredContext->PSSetSamplers(0,1,&info.samplerState);

	SetConstantBuffer(&sceneBuffer,CB_CAMERA);
	SetConstantBuffer(&info.objectCB,CB_OBJECT);

	deferredContext->VSSetConstantBuffers(0, NUMCONSTBUFFS, constantBuffers);
	deferredContext->PSSetConstantBuffers(0, NUMCONSTBUFFS, constantBuffers);

	D3D11_MAPPED_SUBRESOURCE msr1;
	deferredContext->Map(info.objectCB, NULL, D3D11_MAP_WRITE_DISCARD, 0, &msr1);
	memcpy(msr1.pData, &info.object, sizeof(OBJECT));
	deferredContext->Unmap(info.objectCB, 0);

	D3D11_MAPPED_SUBRESOURCE msr;
	deferredContext->Map(sceneBuffer, NULL, D3D11_MAP_WRITE_DISCARD, 0, &msr);
	memcpy(msr.pData, &sScene, sizeof(SCENE));
	deferredContext->Unmap(sceneBuffer, 0);

	deferredContext->DrawIndexed(info.numIndices, 0, 0);
}

bool DirectX11::Render(){

	FLOAT color[4] = { 0.85f, 0.4f, 0.5f, 1.0f };
	FLOAT color2[4] = { 0.05f, 0.8f, 0.15f, 1.0f };
	deviceContext->ClearRenderTargetView(rtv,color);
	deviceContext->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH, FAR_PLANE, 0);
	deviceContext->ClearRenderTargetView(postProcessRTV,color2);
	deviceContext->ClearDepthStencilView(postProcessDSV, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, FAR_PLANE, NEAR_PLANE);


	//Render Scene to Offscreen texture
	deferredContext->FinishCommandList(FALSE,&commandList);
	deviceContext->ExecuteCommandList(commandList,FALSE);
	commandList->Release();

	//Set Pipeline state for clip space quad
	deviceContext->OMSetRenderTargets(1,&rtv,dsv);
	deviceContext->RSSetViewports(1,&viewport);
	deviceContext->IASetInputLayout(nullptr);
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	deviceContext->PSSetShaderResources(0,1,&postProcessTexture);
	deviceContext->PSSetSamplers(0,1,&samplerState);

	deviceContext->VSSetShader(postProcessVS,NULL,NULL);
	deviceContext->GSSetShader(postProcessGS,NULL,NULL);
	deviceContext->PSSetShader(postProcessPS,NULL,NULL);

	deviceContext->Draw(1,0);
	deviceContext->ClearState();


	swapChain->Present(0,0);
	return true;
}

void DirectX11::SetConstantBuffer(ID3D11Buffer** buffer,ConstBuffSlots slot)
{
	constantBuffers[slot] = *buffer;
}

void DirectX11::LoadTexture(wchar_t* fileName,std::string key)
{
	std::wstring filePath = L"../Resources/Images/";
	filePath += fileName;
	ID3D11ShaderResourceView* srv;
	HRESULT hr = CreateDDSTextureFromFile(device,filePath.c_str(),NULL,&srv);
	ERRORCHECK(hr,L"ERROR: Failed to create SRV in DirectX11::LoadTexture()");
	if(S_OK == hr){
		if( nullptr == textures[key])
			textures[key] = srv;
			//textures.insert(textures.cbegin(),std::pair<std::string,ID3D11ShaderResourceView*>(key,srv));
	}
}

void DirectX11::InitializePostProcessStuff()
{

	ID3D11Texture2D* postProcessBackbuffer;
	CD3D11_TEXTURE2D_DESC postProcessBBDesc(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,BACKBUFFER_WIDTH,BACKBUFFER_HEIGHT,
		1,1,D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE);
	HRESULT hr = device->CreateTexture2D(&postProcessBBDesc,NULL,&postProcessBackbuffer);
	ERRORCHECK(hr,L"ERROR: problem creating postProcessBackbuffer");

	ID3D11Texture2D* postProcessDepth;
	CD3D11_TEXTURE2D_DESC postProcessDepthDesc(DXGI_FORMAT_D32_FLOAT,postProcessBBDesc.Width,postProcessBBDesc.Height,1,
		1,D3D11_BIND_DEPTH_STENCIL);
	hr = device->CreateTexture2D(&postProcessDepthDesc,NULL,&postProcessDepth);
	ERRORCHECK(hr,L"Error: problem creating postProcessDepth buffer");

	CD3D11_DEPTH_STENCIL_VIEW_DESC postProcessDSVDesc(D3D11_DSV_DIMENSION_TEXTURE2D,postProcessDepthDesc.Format);
	hr = device->CreateDepthStencilView(postProcessDepth,&postProcessDSVDesc,&postProcessDSV);
	ERRORCHECK(hr,L"ERROR: Failed to create Post Process DSV");

	hr = device->CreateRenderTargetView(postProcessBackbuffer,NULL,&postProcessRTV);
	ERRORCHECK(hr,L"ERROR: Failed to create ppRTV");
	hr = device->CreateShaderResourceView(postProcessBackbuffer,NULL,&postProcessTexture);
	ERRORCHECK(hr,L"ERROR: Failed to create ppTex");

	postProcessViewport.Width = postProcessBBDesc.Width;
	postProcessViewport.Height = postProcessBBDesc.Height;
	postProcessViewport.MinDepth = 0;
	postProcessViewport.MaxDepth = 1;
	postProcessViewport.TopLeftX = 0;
	postProcessViewport.TopLeftY = 0;

	SAFE_RELEASE(postProcessDepth);
	SAFE_RELEASE(postProcessBackbuffer);
}

void DirectX11::RenderDebug(View* camera)
{
	FLOAT color[4] = { 0.85f, 0.4f, 0.5f, 1.0f };
	FLOAT color2[4] = { 0.05f, 0.8f, 0.15f, 1.0f };
	deviceContext->ClearRenderTargetView(rtv,color);
	deviceContext->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH, FAR_PLANE, 0);
	deviceContext->ClearRenderTargetView(postProcessRTV,color2);
	deviceContext->ClearDepthStencilView(postProcessDSV, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, FAR_PLANE, NEAR_PLANE);

	ID3D11Buffer* sceneBuffer = camera->GetBuffer();
	SCENE sScene = *camera->GetStruct();
	OBJECT obj;
	SetConstantBuffer(&sceneBuffer,CB_CAMERA);
	SetConstantBuffer(&sceneBuffer,CB_OBJECT);

	D3D11_MAPPED_SUBRESOURCE msr;
	deferredContext->Map(sceneBuffer, NULL, D3D11_MAP_WRITE_DISCARD, 0, &msr);
	memcpy(msr.pData, &sScene, sizeof(SCENE));
	deferredContext->Unmap(sceneBuffer, 0);

	deferredContext->OMSetRenderTargets(1,&postProcessRTV,postProcessDSV);
	deferredContext->RSSetViewports(1,&postProcessViewport);
	deferredContext->IASetInputLayout(nullptr);
	deferredContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	deferredContext->GSSetConstantBuffers(0, NUMCONSTBUFFS, constantBuffers);
	deferredContext->PSSetSamplers(0,1,&samplerState);

	deferredContext->VSSetShader(lightRepresentationVS,NULL,NULL);
	deferredContext->GSSetShader(lightRepresentationGS,NULL,NULL);
	deferredContext->PSSetShader(lightRepresentationPS,NULL,NULL);

	deferredContext->Draw(1,0);

	//Render Scene to Offscreen texture
	deferredContext->FinishCommandList(FALSE,&commandList);
	deviceContext->ExecuteCommandList(commandList,FALSE);
	commandList->Release();

	//Set Pipeline state for clip space quad
	deviceContext->OMSetRenderTargets(1,&rtv,dsv);
	deviceContext->RSSetViewports(1,&viewport);
	deviceContext->IASetInputLayout(nullptr);
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	deviceContext->PSSetShaderResources(0,1,&postProcessTexture);
	deviceContext->PSSetSamplers(0,1,&samplerState);

	deviceContext->VSSetShader(postProcessVS,NULL,NULL);
	deviceContext->GSSetShader(postProcessGS,NULL,NULL);
	deviceContext->PSSetShader(postProcessPS,NULL,NULL);

	deviceContext->Draw(1,0);
	deviceContext->ClearState();


	swapChain->Present(0,0);
}




